This example mirrors modulate.ipynb.

In [1]:
from pathlib import Path

from ipyniivue import download_dataset

BASE_API_URL = "https://niivue.com/demos/images/"
DATA_FOLDER = Path("images")

# Download data for example
download_dataset(
    BASE_API_URL,
    DATA_FOLDER,
    files=[
        "FA.nii.gz",
        "V1.nii.gz",
    ],
)
FA.nii.gz already exists.
V1.nii.gz already exists.
Dataset downloaded successfully to images.
In [2]:
import ipywidgets as widgets
from IPython.display import display

import ipyniivue

nv = ipyniivue.NiiVue()

# Set options
nv.opts.back_color = (0.0, 0.0, 0.2, 1.0)
nv.opts.show_3d_crosshair = True
nv.opts.drag_mode = ipyniivue.DragMode.PAN
nv.opts.yoke_3d_to_2d_zoom = True
nv.opts.crosshair_width = 0.1
nv.opts.is_force_mouse_click_to_voxel_centers = True
nv.opts.is_nearest_interpolation = True
nv.scene.crosshair_pos = [0.51, 0.51, 0.51]

# Create Volumes
vol_fa = ipyniivue.Volume(path=DATA_FOLDER / "FA.nii.gz", opacity=1.0)
vol_v1 = ipyniivue.Volume(path=DATA_FOLDER / "V1.nii.gz", opacity=1.0)

# Load volumes
nv.load_volumes([vol_fa, vol_v1])

# --- UI Controls ---

mode_dropdown = widgets.Dropdown(
    options=["FA", "V1", "V1 modulated by FA", "Lines", "Lines modulated by FA"],
    value="Lines modulated by FA",
    description="Display:",
    style={"description_width": "initial"},
)

slider_min = widgets.FloatSlider(
    min=0, max=1.0, step=0.01, value=0.0, description="FAmin", continuous_update=True
)

slider_max = widgets.FloatSlider(
    min=0, max=1.0, step=0.01, value=1.0, description="FAmax", continuous_update=True
)

check_clip = widgets.Checkbox(value=True, description="ClipDark")

location_label = widgets.Label(value="Location: ")

# --- Callbacks ---


def update_view(change=None):
    """Update view."""
    mode = mode_dropdown.value

    # Opacity Handling
    if mode == "FA":  # Index 0
        vol_fa.opacity = 1.0
        vol_v1.opacity = 0.0
    elif mode in ["Lines", "Lines modulated by FA"]:  # Index 3, 4
        vol_fa.opacity = 1.0
        vol_v1.opacity = 1.0
    else:  # V1, V1 modulated by FA (Index 1, 2)
        vol_fa.opacity = 0.0
        vol_v1.opacity = 1.0

    # Modulation Handling
    # Modes 2 and 4 use modulation
    if mode in ["V1 modulated by FA", "Lines modulated by FA"]:
        nv.set_modulation_image(vol_v1.id, vol_fa.id)
    else:
        nv.set_modulation_image(vol_v1.id, "")

    # Shader Handling
    # Modes 3 and 4 use V1SliceShader
    nv.opts.is_v1_slice_shader = mode in ["Lines", "Lines modulated by FA"]


def update_contrast(change=None):
    """Update contrast."""
    mn = slider_min.value
    mx = slider_max.value
    vol_fa.cal_min = min(mn, mx)
    vol_fa.cal_max = max(mn, mx)


def update_clip(change=None):
    """Update clip dark."""
    nv.opts.is_alpha_clip_dark = check_clip.value


def on_location_change(data):
    """Update location string."""
    if "string" in data:
        location_label.value = "Location: " + data["string"]


# --- Bind Events ---

mode_dropdown.observe(update_view, names="value")
slider_min.observe(update_contrast, names="value")
slider_max.observe(update_contrast, names="value")
check_clip.observe(update_clip, names="value")
nv.on_location_change(on_location_change)

# --- Initialization ---

update_clip()
nv.on_canvas_attached(update_view)

# --- Layout and Display ---

ui = widgets.VBox(
    [
        widgets.HBox([mode_dropdown, check_clip]),
        widgets.HBox([slider_min, slider_max]),
        nv,
        location_label,
    ]
)

display(ui)